#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""A builder object for <ServiceConfiguration>."""

namespace Grawlix.Services
import C5
import System
import System.Xml
import System.Xml.XPath

class ServiceConfigurationBuilder:
"""Build up a <ServiceConfiguration> object."""
	
	def constructor():
	"""Defaultly initializes the builder."""
		pass
	
	def constructor([required] config as ServiceConfiguration):
	"""
	Initializes the builder from an existing set of configuration.
	Param config: The configuration to initialize from.
	"""
		_friendlyName = config.FriendlyName
		# FIXME: AddAll broken due to boo bug.
		for converter in config.Converters:
			_converters.Add(converter)
		for service in config.Services:
			_services.Add(service)
	
	static def FromFile(
		[required] friendlyName as string,
		[required] location as string):
	"""
	Load an XML configuration file.
	Param friendlyName: The friendlyName to give the provider.
	Param location: The URI of the file to load.
	Returns: The initialized ServiceConfigurationBuilder.
	Remarks: This is a blocking operation.
	"""
		document = XPathDocument(location)
		return FromXml(friendlyName, document)
	
	static def FromXml(
		[required] friendlyName as string,
		[required] root as IXPathNavigable):
	"""
	Construct from a specified configuration stream.
	Param friendlyName: The friendlyName to give the provider.
	Param root: The starting XML node to analyze.
	Returns: An initialized ServiceConfigurationBuilder.
	"""
		builder = ServiceConfigurationBuilder()
		builder.FriendlyName = friendlyName
		rootNavigator = root.CreateNavigator()
		if rootNavigator.NodeType == XPathNodeType.Root:
			unless rootNavigator.MoveToChild(XPathNodeType.Element):
				raise FileFormatException(
					"Unable to parse an XML document provided for " +
					"${friendlyName} configuration. " +
					"The document contains no element nodes. " +
					"Ensure that provided XML documents contain XML data.")
		converters = GetExclusiveGrandchildren(
			friendlyName,
			rootNavigator,
			'converters')
		for converter in converters:
			AddConverter(builder, converter)
		services = GetExclusiveGrandchildren(
			friendlyName,
			rootNavigator,
			'services')
		for service in services:
			AddService(builder, service)
		return builder
	
	private static def GetExclusiveGrandchildren(
		friendlyName as string,
		parent as XPathNavigator,
		nodeName as string):
	"""
	Finds all type converters to add and add them.
	Param friendlyName: The friendlyName of the provider.
	Param parent: An navigator pointing to the parent node.
	Param nodeName: The name of the node to search for.
	Returns: A navigator pointing to the exclusive child, or null.
	"""
		childNodes = parent.Select(nodeName)
		if childNodes.Count == 1:
			return childNodes.Current.SelectChildren(XPathNodeType.Element)
		elif childNodes.Count == 0:
			return null
#		raise FileFormatException(
#			"Error parsing an XML document provided for " +
#			"${friendlyName} configuration. " +
#			"Multiple (${converterNodes.Count}) " +
#			"'${nodeName}' elements were found, " +
#			"whereas a maximum of one is expected. " +
#			"Multiple sections should be merged into one.")
	
	private static def AddConverter(
		builder as ServiceConfigurationBuilder,
		navigator as XPathNavigator):
	"""
	Adds a converter from configuration to the builder.
	Param builder: The builder to add to.
	Param navigator: The cursor to read from.
	"""
		name = navigator.Name
#		attrib = navigator.GetAttribute()
#		if node.Attributes.Count != 1 or \
#			node.Attributes[0].Name != "type":
#			raise Xml.XmlException(
#				"A configured converter node (${name}) must " +
#				"contain a single attribute named 'type' which " +
#				"specifies the assembly-qualified name of the type.")
#		# TODO: More validation needed?
#		typeName = node.Attributes[0].Value
#		builder.Converters.Add(ConverterInfo(name, typeName))
	
	private static def AddService(
		builder as ServiceConfigurationBuilder,
		node as XmlNode):
	"""
	Adds a service from configuration to the builder.
	Param builder: The builder to add to.
	Param node: The node to read from.
	"""
		name = node.Name
		if node.Attributes.Count != 1 or \
			node.Attributes[0].Name != "type":
			raise Xml.XmlException(
				"A configured service node (${name}) must " +
				"contain a single attribute named 'type' which " +
				"specifies the assembly-qualified name of the type.")
		# TODO: More validation needed?
		typeName = node.Attributes[0].Value
		dictionary = ReadDictionary(node)
		builder.Services.Add(ServiceInfo(name, typeName, dictionary))
	
	private static def ReadDictionary(dictionaryNode as XmlNode):
	"""
	Read in a string-based dictionary and return it.
	Param dictionaryNode: The root dictionary node.
		Attributes and name are ignored.
	Returns: The dictionary of key-value strings.
	"""
		dict = HashDictionary[of string, string]()
		for node as XmlNode in dictionaryNode.ChildNodes:
			continue unless node.NodeType == XmlNodeType.Element
			# TODO: Validation?
			key = node.Name
			if node.Attributes.Count != 0:
				raise Xml.XmlException(
					"A dictionary key-value pair (key=${key}) " +
					"should not specify any attributes, " +
					"only the key and value.")
			# TODO: Validation?
			dict.Add(key, node.InnerXml)
		return dict
	
	override def ToString():
	"""
	Gets a string representation for this object.
	Returns: A string representation for this object.
	"""
		return "ServiceConfigurationBuilder ${FriendlyName}"
	
	Configuration:
	"""The <ServiceConfiguration> object based on the current data."""
		get:
			return ServiceConfiguration(_friendlyName, _converters, _services)
	
	[property(FriendlyName)]
	private _friendlyName = "Untitled"
	"""A friendly name for the provider."""
	
	Converters as IList[of ConverterInfo]:
	"""The list of type converters to provide."""
		get:
			return _converters
	
	private _converters = LinkedList[of ConverterInfo]()
	"""The list of type converters to provide."""
	
	Services as IList[of ServiceInfo]:
	"""The list of services to provide."""
		get:
			return _services
	
	private _services = LinkedList[of ServiceInfo]()
	"""The list of services to provide."""
	
	
	
